import string
from .lock import ReentrantRedLock

try:
    import redis
    from redis.sentinel import Sentinel
except ImportError:
    # We can allow custom provider only usage without redis-py being installed
    redis = None


__all__ = ('RedisPlugin,Support Sentinal', )
__version__ = '0.0.1'


CHARACTERS = string.ascii_letters + string.digits


class FlaskRedis(object):

    REDIS_SENTINEL_ENABLE = 'REDIS_SENTINEL_ENABLE'
    REDIS_SENTINEL_NODES = 'REDIS_SENTINEL_NODES'
    REDIS_SENTINEL_MASTER_NAME = 'REDIS_SENTINEL_MASTER_NAME'
    REDIS_SENTINEL_SOCKET_TIMEOUT = 'REDIS_SENTINEL_SOCKET_TIMEOUT'
    REDIS_HOST = 'REDIS_HOST'
    REDIS_PORT = 'REDIS_PORT'
    REDIS_DB = 'REDIS_DB'
    instances = dict()

    def __init__(self, app=None, strict=True, config_prefix='REDIS', **kwargs):
        self._redis_client = None
        self.provider_class = redis.StrictRedis if strict else redis.Redis
        self.provider_kwargs = kwargs
        self.config_prefix = config_prefix

        self.redis_sentinel_enable = None
        self.redis_sentinel_nodes = None
        self.redis_sentinel_master_name = None
        self.redis_sentinel_socket_timeout = None
        self.redis_sentinel_passwd = None
        self.redis_sentinel_cache_db = None
        self.master_address = None

        self.redis_host = None
        self.redis_port = None
        self.redis_db = None
        self._sentinel = None
        self.app = app

        if app is not None:
            self.init_app(app)

    # def get_master_address(self):
    #     master_address = self.sentinel_manager.discover_master(
    #         self.service_name)
    #     if self.is_master:
    #         if self.master_address is None:
    #             self.master_address = master_address
    #         elif master_address != self.master_address:
    #             # Master address changed, disconnect all clients in this pool
    #             self.disconnect()
    #     return master_address

    @classmethod
    def from_custom_provider(cls, provider, app=None, **kwargs):
        assert provider is not None, 'your custom provider is None, come on'

        # We never pass the app parameter here, so we can call init_app
        # ourselves later, after the provider class has been set
        instance = cls(**kwargs)

        instance.provider_class = provider
        if app is not None:
            instance.init_app(app)
        return instance

    def init_app(self, app, **kwargs):
        self.redis_sentinel_enable = app.config.get(self.REDIS_SENTINEL_ENABLE, False)
        self.redis_sentinel_nodes = app.config.get(self.REDIS_SENTINEL_NODES, [])
        self.redis_sentinel_master_name = app.config.get(self.REDIS_SENTINEL_MASTER_NAME, None)
        self.redis_sentinel_socket_timeout = app.config.get(self.REDIS_SENTINEL_SOCKET_TIMEOUT, 0.1)

        if not self.redis_sentinel_enable:
            self.redis_host = app.config.get(self.REDIS_HOST, 'localhost')
            self.redis_port = app.config.get(self.REDIS_PORT, 6379)
            self.redis_db = app.config.get(self.REDIS_DB, 0)
            redis_url = 'redis://%s:%s/%s' % (self.redis_host, self.redis_port, self.redis_db)
            self.provider_kwargs.update(kwargs)
            self._redis_client = self.provider_class.from_url(redis_url, **self.provider_kwargs)
        else:
            self.redis_sentinel_passwd = app.config.get('REDIS_SENTINEL_PASSWD', None)
            self.redis_sentinel_cache_db = app.config.get('REDIS_SENTINEL_CACHE_DB', None)
            self.provider_kwargs.update(
                {'password': self.redis_sentinel_passwd, 'db': self.redis_sentinel_cache_db})
            self._sentinel = Sentinel(self.redis_sentinel_nodes,
                                      socket_timeout=self.redis_sentinel_socket_timeout,
                                      **self.provider_kwargs)
            self._sentinel.discover_master(self.redis_sentinel_master_name)
            app.logger.info(">>>>>>>>>>>>>> REDIS SENTINEL INFO <<<<<<<<<<<<<<<<<")
            app.logger.info(self._sentinel)

        if not hasattr(app, 'extensions'):
            app.extensions = {}
        app.extensions[self.config_prefix.lower()] = self

    @property
    def locker(self):
        _locker = self.instances.get("locker")
        if not _locker:
            _locker = ReentrantRedLock(connection_details=self.master_strict_redis())
            self.instances["locker"] = _locker
        try:
            self.sentinel_master.set('check_online', "ok")
        except Exception as e:
            self.app.logger.error("redis master has gone away, switch master...: %r" % e, exc_info=1)
            self.discover_masters()
            _locker = ReentrantRedLock(connection_details=self.master_strict_redis())
            self.instances["locker"] = _locker
        return _locker

    def discover_masters(self, master=None):
        if not self.redis_sentinel_enable:
            raise NotImplementedError("当前配置不支持Redis Sentinel配置")
        return self._sentinel.discover_master(master or self.redis_sentinel_master_name)

    def master_strict_redis(self):
        masters = self.discover_masters()
        clients = []
        for host, port in [masters]:
            r = redis.StrictRedis(host=host, port=port, db=self.redis_sentinel_cache_db,
                                  password=self.redis_sentinel_passwd)
            clients.append(r)
        return clients

    def discover_slaves(self, slave=None):
        if not self.redis_sentinel_enable:
            raise NotImplementedError("当前配置不支持Redis Sentinel配置")
        return self._sentinel.discover_slaves(slave or self.redis_sentinel_master_name)

    @property
    def sentinel_master(self):
        """
            The master and slave objects are normal StrictRedis instances with their connection pool
            bound to the Sentinel instance.
        """
        return self._sentinel.master_for(self.redis_sentinel_master_name,
                                         socket_timeout=self.redis_sentinel_socket_timeout)

    @property
    def sentinel_slave(self):
        """
            The master and slave objects are normal StrictRedis instances with their connection pool
            bound to the Sentinel instance.
        """
        return self._sentinel.slave_for(self.redis_sentinel_master_name,
                                        socket_timeout=self.redis_sentinel_socket_timeout)

    def __getattr__(self, name):
        return getattr(self._redis_client, name)

    def __getitem__(self, name):
        return self._redis_client[name]

    def __setitem__(self, name, value):
        self._redis_client[name] = value

    def __delitem__(self, name):
        del self._redis_client[name]
